﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using ModelViewerAux;
using System.ComponentModel;
using Microsoft.Xna.Framework;


namespace ModelViewer
{
    /// <summary>
    /// Animation clip player
    /// </summary>
    public class ClipPlayer
    {
        #region Fields

        /// <summary>
        /// Current position in time in the clip
        /// </summary>
        private float position = 0;

        /// <summary>
        /// The clip we are playing
        /// </summary>
        private AnimationClip clip = null;

        /// <summary>
        /// The bone information while playing
        /// </summary>
        private BoneInfo[] boneInfos;

        /// <summary>
        /// The number of bones
        /// </summary>
        private int boneCnt;

        /// <summary>
        /// An assignment viewer model
        /// </summary>
        private ViewerModel model = null;

        #endregion

        #region Properties

        /// <summary>
        /// The position in the animation
        /// </summary>
        [Browsable(false)]
        public float Position
        {
            get { return position; }
            set 
            {
                if (value > Duration)
                    position = Duration;
                else
                    position = value; 

                Update(); 
            }
        }

        /// <summary>
        /// The associated animation clip
        /// </summary>
        [Browsable(false)]
        public AnimationClip Clip { get { return clip; } }

        /// <summary>
        /// The clip duration
        /// </summary>
        [Browsable(false)]
        public float Duration { get { return (float)clip.Duration; } }

        /// <summary>
        /// A model this animation is assigned to. It will play on that model.
        /// </summary>
        [Browsable(false)]
        public ViewerModel Model { get { return model; } set { model = value; SetModel(); } }

        #endregion

        /// <summary>
        /// Constructor for the player
        /// </summary>
        /// <param name="clip"></param>
        public ClipPlayer(AnimationClip clip)
        {
            this.clip = clip;

            boneCnt = clip.Bones.Count;
            boneInfos = new BoneInfo[boneCnt];

            // Some initial setup
            for(int b=0;  b<boneInfos.Length;  b++)
            {
                boneInfos[b] = new BoneInfo(clip.Bones[b]);
            }

            Rewind();
        }


        /// <summary>
        /// Reset back to time zero.
        /// </summary>
        public void Rewind()
        {
            position = 0;
            for (int b = 0; b < boneCnt; b++)
            {
                boneInfos[b].SetPosition(0);
            }
        }

        /// <summary>
        /// Update the clip position
        /// </summary>
        /// <param name="delta"></param>
        private void Update()
        {
            foreach (BoneInfo bone in boneInfos)
            {
                bone.SetPosition(position);
            }
        }

        private void SetModel()
        {
            foreach (BoneInfo bone in boneInfos)
            {
                bone.SetModel(model);
            }
        }


        /// <summary>
        /// Information about a bone we are animating
        /// </summary>
        private class BoneInfo
        {
            /// <summary>
            /// Constructor
            /// </summary>
            /// <param name="bone"></param>
            public BoneInfo(AnimationClip.Bone bone)
            {
                this.Bone = bone;
                SetKeyframes();
                SetPosition(0);
            }

            /// <summary>
            /// The current keyframe
            /// </summary>
            private int currentKeyframe = 0;

            /// <summary>
            /// Bone in a model that this keyframe bone is assigned to
            /// </summary>
            private Bone assignedBone = null;

            /// <summary>
            /// The current keyframe. OUr position is a time such that the 
            /// we are greater than or equal to this keyframe's time and less
            /// than the next keyframes time.
            /// </summary>
            public int CurrentKeyframe { get { return currentKeyframe; } }

            private Vector3 scale;
            private Quaternion rotation;
            public Vector3 translation;

            /// <summary>
            /// We are at a location between Keyframe1 and Keyframe2 such 
            /// that Keyframe1's time is less than or equal to the current position
            /// </summary>
            public AnimationClip.Keyframe Keyframe1;

            /// <summary>
            /// Second keyframe value
            /// </summary>
            public AnimationClip.Keyframe Keyframe2;

            /// <summary>
            /// The bone in the actual animation clip
            /// </summary>
            public AnimationClip.Bone Bone { get; set; }

            /// <summary>
            /// Set the bone based on the supplied position value
            /// </summary>
            /// <param name="position"></param>
            public void SetPosition(float position)
            {
                List<AnimationClip.Keyframe> keyframes = Bone.Keyframes;
                if (keyframes.Count == 0)
                    return;

                while (position < Keyframe1.Time && CurrentKeyframe > 0)
                {
                    // We need to move backwards in time
                    currentKeyframe--;
                    SetKeyframes();
                }


                while (position >= Keyframe2.Time && CurrentKeyframe < Bone.Keyframes.Count - 2)
                {
                    // We need to move forwards in time
                    currentKeyframe++;
                    SetKeyframes();
                }

                if (Keyframe1 == Keyframe2)
                {
                    // Keyframes are equal
                    scale = Keyframe1.Scale;
                    rotation = Keyframe1.Rotation;
                    translation = Keyframe1.Translation;
                }
                else
                {
                    // Interpolate between keyframes
                    float t = (float)((position - Keyframe1.Time) / (Keyframe2.Time - Keyframe1.Time));

                    scale = Vector3.Lerp(Keyframe1.Scale, Keyframe2.Scale, t);
                    rotation = Quaternion.Slerp(Keyframe1.Rotation, Keyframe2.Rotation, t);
                    translation = Vector3.Lerp(Keyframe1.Translation, Keyframe2.Translation, t);
                }

                if (assignedBone != null)
                {
                    // Send to the model
                    // Make it a matrix first
                    Matrix m = Matrix.CreateScale(scale) * Matrix.CreateFromQuaternion(rotation);
                    m.Translation = translation;
                    assignedBone.SetCompleteTransform(m);
                }
            }



            /// <summary>
            /// Set the keyframes to a valid value relative to 
            /// the current keyframe
            /// </summary>
            private void SetKeyframes()
            {
                if (Bone.Keyframes.Count > 0)
                {
                    Keyframe1 = Bone.Keyframes[CurrentKeyframe];
                    if (CurrentKeyframe == Bone.Keyframes.Count - 1)
                        Keyframe2 = Keyframe1;
                    else
                        Keyframe2 = Bone.Keyframes[CurrentKeyframe + 1];
                }
                else
                {
                    Keyframe1 = null;
                    Keyframe2 = null;
                }
            }

            public void SetModel(ViewerModel model)
            {
                // Find this bone
                assignedBone = model.FindBone(Bone.Name);
                
            }
        }


    }
}
